﻿using Microsoft.Extensions.Logging;
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Threading;

namespace Atomic
{
    //public class  ConnectionManager
    //{
    //    private readonly ILogger _logger;
    //    public ConcurrentDictionary<string, SocketConnection> Connections { get; }
    //    public int MaxConnectionNum { get; set; } = 5;

    //    public ConnectionManager(ILoggerFactory loggerFactory)
    //    {
    //        Connections = new ConcurrentDictionary<string, SocketConnection>(StringComparer.OrdinalIgnoreCase);
    //        _logger = loggerFactory.CreateLogger("JTActiveSafety.Hosting.Server"); 
    //    }

    //    public int TotalConnectionCount
    //    {
    //        get
    //        {
    //            return Connections.Count;
    //        }
    //    }

    //    public void Add(SocketConnection connection)
    //    {
    //        if (Connections.TryAdd(connection.ConnectionId, connection)) {
    //            if (_logger.IsEnabled(LogLevel.Information))
    //                _logger.LogInformation($"[Connection Start]:{connection.RemoteEndPoint} {connection.ConnectionId}");
    //        }
    //    }
    //    public void Remove(string connectionId)
    //    {
    //        if (Connections.TryRemove(connectionId, out var connection))
    //        {
    //            if (_logger.IsEnabled(LogLevel.Information))
    //                _logger.LogInformation($"[Connection Remove]:{connection.RemoteEndPoint} {connection.ConnectionId}");
    //        }
    //    }
    //}

    public class ConnectionManager
    {
        private readonly ConcurrentDictionary<long, ConnectionReference> _connectionReferences = new ConcurrentDictionary<long, ConnectionReference>();
        private readonly IKestrelTrace _trace;

        public ConnectionManager(IKestrelTrace trace, long? upgradedConnectionLimit)
            : this(trace, GetCounter(upgradedConnectionLimit))
        {
        }

        public ConnectionManager(IKestrelTrace trace, ResourceCounter upgradedConnections)
        {
            UpgradedConnectionCount = upgradedConnections;
            _trace = trace;
        }

        /// <summary>
        /// Connections that have been switched to a different protocol.
        /// </summary>
        public ResourceCounter UpgradedConnectionCount { get; }

        public void AddConnection(long id, ConnectionReference connectionReference)
        {
            if (!_connectionReferences.TryAdd(id, connectionReference))
            {
                throw new ArgumentException(nameof(id));
            }
        }

        public void RemoveConnection(long id)
        {
            if (!_connectionReferences.TryRemove(id, out var reference))
            {
                throw new ArgumentException(nameof(id));
            }

            if (reference.TryGetConnection(out var connection))
            {
                connection.Complete();
            }
        }

        public void Walk(Action<KestrelConnection> callback)
        {
            foreach (var kvp in _connectionReferences)
            {
                var reference = kvp.Value;

                if (reference.TryGetConnection(out var connection))
                {
                    callback(connection);
                }
                else if (_connectionReferences.TryRemove(kvp.Key, out reference))
                {
                    // It's safe to modify the ConcurrentDictionary in the foreach.
                    // The connection reference has become unrooted because the application never completed.
                    _trace.ApplicationNeverCompleted(reference.ConnectionId);
                    reference.StopTrasnsportTracking();
                }

                // If both conditions are false, the connection was removed during the heartbeat.
            }
        }

        private static ResourceCounter GetCounter(long? number)
            => number.HasValue
                ? ResourceCounter.Quota(number.Value)
                : ResourceCounter.Unlimited;
    }

    /// <summary>
    /// 资源计数器
    /// </summary>
    public abstract class ResourceCounter
    {
        public abstract bool TryLockOne();
        public abstract void ReleaseOne();

        public static ResourceCounter Unlimited { get; } = new UnlimitedCounter();
        public static ResourceCounter Quota(long amount) => new FiniteCounter(amount);

        private class UnlimitedCounter : ResourceCounter
        {
            public override bool TryLockOne() => true;
            public override void ReleaseOne()
            {
            }
        }

        internal class FiniteCounter : ResourceCounter
        {
            private readonly long _max;
            private long _count;

            public FiniteCounter(long max)
            {
                if (max < 0)
                {
                    throw new ArgumentOutOfRangeException("non negative number required.");
                }

                _max = max;
            }

            public override bool TryLockOne()
            {
                var count = _count;

                // Exit if count == MaxValue as incrementing would overflow.

                while (count < _max && count != long.MaxValue)
                {
                    var prev = Interlocked.CompareExchange(ref _count, count + 1, count);
                    if (prev == count)
                    {
                        return true;
                    }

                    // Another thread changed the count before us. Try again with the new counter value.
                    count = prev;
                }

                return false;
            }

            public override void ReleaseOne()
            {
                Interlocked.Decrement(ref _count);

                Debug.Assert(_count >= 0, "Resource count is negative. More resources were released than were locked.");
            }

            // for testing
            internal long Count
            {
                get => _count;
                set => _count = value;
            }
        }
    }
}